package com.googlecode.entity_dao.core.criteria;

import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.entity_dao.core.util.ReflectionHelper;

/**
 * General purpose persistent entity criteria implementation acting as container for {@link NestedPropertyCriterion}
 * instances.
 * 
 * <p>
 * 
 * This class implements the query constraint application logic regarding specific {@link NestedPropertyCriterion}
 * subclasses via default {@link NestedPropertyCriterionVisitor} implementation.
 * 
 * <p>
 * 
 * Additionally, the nested persistent entity property criteria contains features that are common to either all or
 * certain {@link NestedPropertyCriterion} instances:
 * 
 * <ul>
 * <li>filter object as an <em>optional</em> source of filter values (see {@link FilterCriterion} for more information
 * about the filter object and direct filter value concepts)
 * <li>use of {@link AssociationPathRegister} for root {@link Criteria} preprocessing regarding nested subcriteria
 * (association paths defined by {@link NestedPropertyCriterion} instances)
 * </ul>
 * 
 * @see NestedPropertyCriterion
 * @see NestedPropertyCriterionVisitor
 * @see AbstractCriterionGroup
 * 
 * @author vojtech.szocs
 */
public class NestedPropertyCriteria extends
        AbstractCriterionGroup<NestedPropertyCriterion<NestedPropertyCriterionVisitor>, NestedPropertyCriterionVisitor> {

    private static final Logger LOG = LoggerFactory.getLogger(NestedPropertyCriteria.class);

    private Object filterObject;

    /**
     * @return Filter object (can be <tt>null</tt>).
     */
    public Object getFilterObject() {
        return filterObject;
    }

    /**
     * @param filterObject Filter object (can be <tt>null</tt>).
     */
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    /**
     * @see com.anasoft.os.daofusion.criteria.AbstractCriterionGroup#getCriterionVisitor(org.hibernate.Criteria)
     */
    @Override
    protected NestedPropertyCriterionVisitor getCriterionVisitor(Criteria targetCriteria) {
        return new DefaultNestedPropertyCriterionVisitor(targetCriteria, new AssociationPathRegister(targetCriteria),
                filterObject);
    }

    /**
     * Default {@link NestedPropertyCriterionVisitor} implementation operating on the given {@link Criteria} instance.
     * 
     * @see NestedPropertyCriterionVisitor
     * 
     * @author vojtech.szocs
     */
    public static class DefaultNestedPropertyCriterionVisitor implements NestedPropertyCriterionVisitor {

        protected final Criteria targetCriteria;
        protected final AssociationPathRegister associationPathRegister;
        protected final Object filterObject;

        public DefaultNestedPropertyCriterionVisitor(Criteria targetCriteria,
                AssociationPathRegister associationPathRegister, Object filterObject) {
            this.targetCriteria = targetCriteria;
            this.associationPathRegister = associationPathRegister;
            this.filterObject = filterObject;
        }

        /**
         * @see com.anasoft.os.daofusion.criteria.NestedPropertyCriterionVisitor#visit(com.anasoft.os.daofusion.criteria.FilterCriterion)
         */
        public void visit(FilterCriterion criterion) {

            // resolve filterObjectValuePaths against the filterObject
            Object[] filterObjectValues = null;

            if (filterObject != null) {
                String[] filterObjectValuePaths = criterion.getFilterObjectValuePaths();

                if (filterObjectValuePaths != null) {
                    filterObjectValues = new Object[filterObjectValuePaths.length];

                    for (int i = 0; i < filterObjectValuePaths.length; i++) {
                        filterObjectValues[i] = ReflectionHelper.resolvePropertyPath(filterObject,
                                filterObjectValuePaths[i]);
                    }
                }
            }

            // apply query constraints to the Criteria instance
            FilterCriterionProvider criterionProvider = criterion.getFilterCriterionProvider();
            Object[] directValues = criterion.getDirectValues();

            if (criterionProvider.enabled(filterObjectValues, directValues)) {
                Criterion propertyFilterCriterion = criterionProvider.getCriterion(criterion.getTargetPropertyName(),
                        filterObjectValues, directValues);

                AssociationPath associationPath = criterion.getAssociationPath();
                associationPathRegister.get(associationPath).add(propertyFilterCriterion);
            } else {
                LOG.info(
                        "Skipping FilterCriterion instance processing for associationPath '{}' / targetPropertyName '{}' - associated filter criterion provider reports to be disabled",
                        criterion.getAssociationPath().toString(), criterion.getTargetPropertyName());
            }
        }

        /**
         * @see com.anasoft.os.daofusion.criteria.NestedPropertyCriterionVisitor#visit(com.anasoft.os.daofusion.criteria.SortCriterion)
         */
        public void visit(SortCriterion criterion) {
            String targetPropertyName = criterion.getTargetPropertyName();

            Order order = criterion.isSortAscending() ? Order.asc(targetPropertyName) : Order.desc(targetPropertyName);

            if (criterion.isIgnoreCase()) {
                order.ignoreCase();
            }

            AssociationPath associationPath = criterion.getAssociationPath();
            associationPathRegister.get(associationPath).addOrder(order);
        }

    }

    @Override
    public String toString() {
        return getObjectList().toString();
    }

}
